﻿#region Usings

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
using System.Xml;
using Yandex.Market.Api.Model;
using Yandex.Market.Api.Validation;

#endregion

namespace Yandex.Market.Api
{
    /// <summary>
    /// </summary>
    public static class YmlCatalogXmlBuilder
    {
        private static readonly NumberFormatInfo _provider;

        static YmlCatalogXmlBuilder()
        {
            _provider = (NumberFormatInfo)NumberFormatInfo.CurrentInfo.Clone();
            _provider.NumberDecimalSeparator = ".";
        }

        /// <summary>
        ///   Сохраняет xml представление объекта <c>YmlCatalog</c> в поток
        /// </summary>
        /// <param name = "catalog">Экземпляр объекта <c>YmlCatalog</c></param>
        /// <param name = "outStream">Выходной поток</param>
        /// <param name = "validate">Определяет будет ли производиться валидация объекта <c>YmlCatalog</c></param>
        public static void YmlCatalogToXml(YmlCatalog catalog, Stream outStream, bool validate)
        {
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Encoding = Encoding.GetEncoding("windows-1251");
            settings.Indent = true;

            using(XmlWriter writer = XmlWriter.Create(outStream, settings))
            {
                writer.WriteProcessingInstruction("xml", "version='1.0' encoding='windows-1251'");
                writer.WriteDocType("yml_catalog", null, "shops.dtd", null);
                string validationErrors = "";
                bool isValid = true;

                if(validate)
                    isValid = Validator.ValidateYmlCatalog(catalog, out validationErrors);

                if(!isValid)
                {
                    writer.WriteComment(string.Format("Validation Errors:{0}{1}", Environment.NewLine, validationErrors));
                    writer.WriteRaw(Environment.NewLine);
                }

                writer.WriteStartElement("yml_catalog");
                writer.WriteAttributeString("date", DateTime.Now.ToString("yyyy-MM-dd HH:mm"));

                WriteShop(catalog.Shop, writer);

                writer.WriteEndElement();

                writer.Flush();
                writer.Close();
            }
        }

        /// <summary>
        ///   Сохраняет xml представление объекта <c>YmlCatalog</c> в файл
        /// </summary>
        /// <param name = "catalog">Экземпляр объекта <c>YmlCatalog</c></param>
        /// <param name = "path">Полный путь к файлу</param>
        /// <param name = "validate">Определяет будет ли производиться валидация объекта <c>YmlCatalog</c></param>
        public static void YmlCatalogToXml(YmlCatalog catalog, string path, bool validate)
        {
            using(Stream outStream = new FileStream(path, FileMode.Create))
                YmlCatalogToXml(catalog, outStream, validate);
        }

        /// <summary>
        ///   Сохраняет xml представление объекта <c>YmlCatalog</c> в строку
        /// </summary>
        /// <param name = "catalog">Экземпляр объекта <c>YmlCatalog</c></param>
        /// <param name = "validate">Определяет будет ли производиться валидация объекта <c>YmlCatalog</c></param>
        /// <returns>Строка, содержащая YML описание</returns>
        public static string YmlCatalogToXml(YmlCatalog catalog, bool validate)
        {
            using(MemoryStream memoryStream = new MemoryStream())
            {
                YmlCatalogToXml(catalog, memoryStream, validate);
                memoryStream.Seek(0, SeekOrigin.Begin);
                using(StreamReader streamReader = new StreamReader(memoryStream, Encoding.GetEncoding("windows-1251")))
                    return streamReader.ReadToEnd();
            }
        }

        #region Shop

        private static void WriteShop(Shop shop, XmlWriter writer)
        {
            writer.WriteStartElement("shop");

            writer.WriteElementString("name", shop.Name);
            writer.WriteElementString("company", shop.Company);
            writer.WriteElementString("url", shop.Url);

            if(!string.IsNullOrEmpty(shop.Phone))
                writer.WriteElementString("phone", shop.Phone);

            if(!string.IsNullOrEmpty(shop.Platform))
                writer.WriteElementString("platform", shop.Platform);

            if(!string.IsNullOrEmpty(shop.Version))
                writer.WriteElementString("version", shop.Version);

            if(!string.IsNullOrEmpty(shop.Agency))
                writer.WriteElementString("agency", shop.Agency);

            if(shop.Emails != null && shop.Emails.Count > 0)
                WriteShopEmails(shop.Emails, writer);

            writer.WriteStartElement("currencies");
            foreach(Currency currency in shop.Currencies)
                WriteShopCurrencies(currency, writer);
            writer.WriteEndElement();

            writer.WriteStartElement("categories");
            foreach(Category category in shop.Categories)
                WriteShopCategories(category, writer);
            writer.WriteEndElement();

            if(shop.Store.HasValue)
                writer.WriteElementString("store", shop.Store.Value.ToString().ToLower());

            if(shop.Pickup.HasValue)
                writer.WriteElementString("pickup", shop.Pickup.Value.ToString().ToLower());

            if(shop.Delivery.HasValue)
                writer.WriteElementString("delivery", shop.Delivery.Value.ToString().ToLower());

            if(shop.DeliveryIncluded.HasValue)
                writer.WriteElementString("deliveryIncluded", shop.DeliveryIncluded.Value.ToString().ToLower());

            if(shop.LocalDeliveryCost.HasValue)
                writer.WriteElementString("local_delivery_cost", shop.LocalDeliveryCost.Value.ToString(_provider));

            if(shop.Adult.HasValue)
                writer.WriteElementString("adult", shop.Adult.Value.ToString().ToLower());

            writer.WriteStartElement("offers");
            foreach(OfferBase offer in shop.Offers)
                WriteShopOffers(offer, writer);
            writer.WriteEndElement();

            writer.WriteEndElement();
        }

        #region Shop Nested Classes

        private static void WriteShopEmails(IEnumerable<string> emails, XmlWriter writer)
        {
            foreach(string email in emails)
                writer.WriteElementString("email", email);
        }

        private static void WriteShopCategories(Category category, XmlWriter writer)
        {
            writer.WriteStartElement("category");
            writer.WriteAttributeString("id", category.ID.ToString());

            if(category.ParentID.HasValue)
                writer.WriteAttributeString("parentId", category.ParentID.Value.ToString());

            if(category.Tid.HasValue)
                writer.WriteAttributeString("tid", category.Tid.Value.ToString());

            if(category.Yid.HasValue)
                writer.WriteAttributeString("yid", category.Yid.Value.ToString());

            writer.WriteString(category.Value);
            writer.WriteEndElement();
        }

        private static void WriteShopCurrencies(Currency currency, XmlWriter writer)
        {
            writer.WriteStartElement("currency");
            writer.WriteAttributeString("id", currency.ID.ToString());

            if(!string.IsNullOrEmpty(currency.Rate))
                writer.WriteAttributeString("rate", currency.Rate);

            if(currency.Plus.HasValue)
                writer.WriteAttributeString("plus", currency.Plus.Value.ToString());

            writer.WriteEndElement();
        }

        #endregion

        #endregion

        #region Offers

        private static void WriteShopOffers(IOffer offer, XmlWriter writer)
        {
            writer.WriteStartElement("offer");
            writer.WriteAttributeString("id", offer.ID.ToString());

            if(offer is OfferVendorModel)
                writer.WriteAttributeString("type", "vendor.model");
            else if(offer is OfferBook)
                writer.WriteAttributeString("type", "book");
            else if(offer is OfferAudioBook)
                writer.WriteAttributeString("type", "audiobook");
            else if(offer is OfferArtistTitle)
                writer.WriteAttributeString("type", "artist.title");
            else if(offer is OfferTour)
                writer.WriteAttributeString("type", "tour");
            else if(offer is OfferEventTicket)
                writer.WriteAttributeString("type", "event-ticket");

            if(offer.Available.HasValue)
                writer.WriteAttributeString("available", offer.Available.Value.ToString().ToLower());

            if(offer.BID.HasValue)
                writer.WriteAttributeString("bid", offer.BID.Value.ToString());

            if(offer.CBID.HasValue)
                writer.WriteAttributeString("cbid", offer.CBID.Value.ToString());

            if(!string.IsNullOrEmpty(offer.Url))
                writer.WriteElementString("url", offer.Url);

            if(!string.IsNullOrEmpty(offer.BuyUrl))
                writer.WriteElementString("buyurl", offer.BuyUrl);

            writer.WriteElementString("price", offer.Price.ToString(_provider));

            if(offer.Wprice.HasValue)
                writer.WriteElementString("wprice", offer.Wprice.Value.ToString(_provider));

            writer.WriteElementString("currencyId", offer.CurrencyId.ToString());

            if(!string.IsNullOrEmpty(offer.Xcategory))
                writer.WriteElementString("xCategory", offer.Xcategory);

            WriteOfferCategories(offer.CategoryIds, writer);

            if (!string.IsNullOrEmpty(offer.Picture))
                writer.WriteElementString("picture", offer.Picture);

            if(offer.Store.HasValue)
                writer.WriteElementString("store", offer.Store.Value.ToString().ToLower());

            if(offer.Pickup.HasValue)
                writer.WriteElementString("pickup", offer.Pickup.Value.ToString().ToLower());

            if(offer.Delivery.HasValue)
                writer.WriteElementString("delivery", offer.Delivery.Value.ToString().ToLower());

            if(offer.DeliveryIncluded.HasValue)
                writer.WriteElementString("deliveryIncluded", offer.DeliveryIncluded.Value.ToString().ToLower());

            if(offer.LocalDeliveryCost.HasValue)
                writer.WriteElementString("local_delivery_cost", offer.LocalDeliveryCost.Value.ToString(_provider));

            if(offer.OrderingTime != null)
                WriteOfferOrderingTime(offer.OrderingTime, writer);

            if(offer is OfferVendorModel)
            {
                OfferVendorModel offerVendorModel = offer as OfferVendorModel;
                WriteOfferVendorModel(offerVendorModel, writer);
            }
            else if(offer is OfferBook)
            {
                OfferBook offerBook = offer as OfferBook;
                WriteOfferBook(offerBook, writer);
            }
            else if(offer is OfferAudioBook)
            {
                OfferAudioBook offerAudioBook = offer as OfferAudioBook;
                WriteOfferAudioBook(offerAudioBook, writer);
            }
            else if(offer is OfferArtistTitle)
            {
                OfferArtistTitle offerArtistTitle = offer as OfferArtistTitle;
                WriteOfferArtistTitle(offerArtistTitle, writer);
            }
            else if(offer is OfferTour)
            {
                OfferTour offerTour = offer as OfferTour;
                WriteOfferTour(offerTour, writer);
            }
            else if(offer is OfferEventTicket)
            {
                OfferEventTicket offerEventTicket = offer as OfferEventTicket;
                WriteOfferEventTicket(offerEventTicket, writer);
            }
            else if (offer is OfferSimple)
            {
                OfferSimple offerSimple = offer as OfferSimple;
                WriteOfferSimple(offerSimple, writer);
            }

            if(!string.IsNullOrEmpty(offer.Aliases))
                writer.WriteElementString("aliases", offer.Aliases);

            if(offer.Additionals != null && offer.Additionals.Count > 0)
                WriteOfferAdditionals(offer.Additionals, writer);

            if(!string.IsNullOrEmpty(offer.Description))
                writer.WriteElementString("description", offer.Description);

            if(!string.IsNullOrEmpty(offer.SalesNotes))
                writer.WriteElementString("sales_notes", offer.SalesNotes);

            if(!string.IsNullOrEmpty(offer.Promo))
                writer.WriteElementString("promo", offer.Promo);

            if(offer.ManufacturerWarranty.HasValue)
                writer.WriteElementString("manufacturer_warranty", offer.ManufacturerWarranty.Value.ToString().ToLower());

            if(!string.IsNullOrEmpty(offer.CountryOfOrigin))
                writer.WriteElementString("country_of_origin", offer.CountryOfOrigin);

            if(offer.Downloadable.HasValue)
                writer.WriteElementString("downloadable", offer.Downloadable.Value.ToString().ToLower());

            if(offer.Adult.HasValue)
                writer.WriteElementString("adult", offer.Adult.Value.ToString().ToLower());

            if(offer.Barcodes != null && offer.Barcodes.Count > 0)
                WriteOfferBarcodes(offer.Barcodes, writer);

            if(offer.Params != null && offer.Params.Count > 0)
                WriteOfferParams(offer.Params, writer);

            writer.WriteEndElement();
        }

        #region Offer Content Models

        #region OfferEventTicket

        private static void WriteOfferEventTicket(OfferEventTicket offerEventTicket, XmlWriter writer)
        {
            writer.WriteElementString("name", offerEventTicket.Name);
            writer.WriteElementString("place", offerEventTicket.Place);

            if(offerEventTicket.Hall != null)
                WriteOfferEventTicketHall(offerEventTicket.Hall, writer);

            if(!string.IsNullOrEmpty(offerEventTicket.HallPart))
                writer.WriteElementString("hall_part", offerEventTicket.HallPart);

            writer.WriteElementString("date", offerEventTicket.Date.ToString("s"));

            if(offerEventTicket.IsPremiere.HasValue)
                writer.WriteElementString("is_premiere", offerEventTicket.IsPremiere.Value.ToString());

            if(offerEventTicket.IsKids.HasValue)
                writer.WriteElementString("is_kids", offerEventTicket.IsKids.Value.ToString());
        }

        private static void WriteOfferEventTicketHall(Hall hall, XmlWriter writer)
        {
            writer.WriteStartElement("hall");
            if(!string.IsNullOrEmpty(hall.Plan))
                writer.WriteAttributeString("plan", hall.Plan);
            writer.WriteString(hall.Value);
            writer.WriteEndElement();
        }

        #endregion

        #region OfferTour

        private static void WriteOfferTour(OfferTour offerTour, XmlWriter writer)
        {
            if(!string.IsNullOrEmpty(offerTour.WorldRegion))
                writer.WriteElementString("worldRegion", offerTour.WorldRegion);

            if(!string.IsNullOrEmpty(offerTour.Country))
                writer.WriteElementString("country", offerTour.Country);

            if(!string.IsNullOrEmpty(offerTour.Region))
                writer.WriteElementString("region", offerTour.Region);

            writer.WriteElementString("days", offerTour.Days.ToString());

            if(offerTour.DataTour != null && offerTour.DataTour.Count > 0)
                WriteOfferTourDataTours(offerTour.DataTour, writer);

            writer.WriteElementString("name", offerTour.Name);

            if(!string.IsNullOrEmpty(offerTour.HotelStars))
                writer.WriteElementString("hotel_stars", offerTour.HotelStars);

            if(!string.IsNullOrEmpty(offerTour.Room))
                writer.WriteElementString("room", offerTour.Room);

            if(!string.IsNullOrEmpty(offerTour.Meal))
                writer.WriteElementString("meal", offerTour.Meal);

            writer.WriteElementString("included", offerTour.Included);
            writer.WriteElementString("transport", offerTour.Transport);

            if(offerTour.PriceMin.HasValue)
                writer.WriteElementString("price_min", offerTour.PriceMin.Value.ToString(_provider));

            if(offerTour.PriceMax.HasValue)
                writer.WriteElementString("price_max", offerTour.PriceMax.Value.ToString(_provider));

            if(!string.IsNullOrEmpty(offerTour.Options))
                writer.WriteElementString("options", offerTour.Options);
        }

        private static void WriteOfferTourDataTours(IEnumerable<DateTime> dataTours, XmlWriter writer)
        {
            foreach(DateTime dataTour in dataTours)
                writer.WriteElementString("dataTour", dataTour.ToString("dd/MM/yy"));
        }

        #endregion

        private static void WriteOfferArtistTitle(OfferArtistTitle offerArtistTitle, XmlWriter writer)
        {
            if(!string.IsNullOrEmpty(offerArtistTitle.Artist))
                writer.WriteElementString("artist", offerArtistTitle.Artist);

            writer.WriteElementString("title", offerArtistTitle.Title);

            if(offerArtistTitle.Year.HasValue)
                writer.WriteElementString("year", offerArtistTitle.Year.Value.ToString());

            if(!string.IsNullOrEmpty(offerArtistTitle.Media))
                writer.WriteElementString("media", offerArtistTitle.Media);

            if(!string.IsNullOrEmpty(offerArtistTitle.Starring))
                writer.WriteElementString("starring", offerArtistTitle.Starring);

            if(!string.IsNullOrEmpty(offerArtistTitle.Director))
                writer.WriteElementString("director", offerArtistTitle.Director);

            if(!string.IsNullOrEmpty(offerArtistTitle.OriginalName))
                writer.WriteElementString("originalName", offerArtistTitle.OriginalName);

            if(!string.IsNullOrEmpty(offerArtistTitle.Country))
                writer.WriteElementString("country", offerArtistTitle.Country);
        }

        private static void WriteOfferAudioBook(OfferAudioBook offerAudioBook, XmlWriter writer)
        {
            if(!string.IsNullOrEmpty(offerAudioBook.Author))
                writer.WriteElementString("author", offerAudioBook.Author);

            writer.WriteElementString("name", offerAudioBook.Name);

            if(!string.IsNullOrEmpty(offerAudioBook.Publisher))
                writer.WriteElementString("publisher", offerAudioBook.Publisher);

            if(!string.IsNullOrEmpty(offerAudioBook.Series))
                writer.WriteElementString("series", offerAudioBook.Series);

            if(offerAudioBook.Year.HasValue)
                writer.WriteElementString("year", offerAudioBook.Year.Value.ToString());

            if(!string.IsNullOrEmpty(offerAudioBook.ISBN))
                writer.WriteElementString("ISBN", offerAudioBook.ISBN);

            if(offerAudioBook.Volume.HasValue)
                writer.WriteElementString("volume", offerAudioBook.Volume.Value.ToString());

            if(offerAudioBook.Part.HasValue)
                writer.WriteElementString("part", offerAudioBook.Part.Value.ToString());

            if(!string.IsNullOrEmpty(offerAudioBook.Language))
                writer.WriteElementString("language", offerAudioBook.Language);

            if(!string.IsNullOrEmpty(offerAudioBook.TableOfContents))
                writer.WriteElementString("table_of_contents", offerAudioBook.TableOfContents);

            if(!string.IsNullOrEmpty(offerAudioBook.PerformedBy))
                writer.WriteElementString("performed_by", offerAudioBook.PerformedBy);

            if(!string.IsNullOrEmpty(offerAudioBook.PerformanceType))
                writer.WriteElementString("performance_type", offerAudioBook.PerformanceType);

            if(!string.IsNullOrEmpty(offerAudioBook.Storage))
                writer.WriteElementString("storage", offerAudioBook.Storage);

            if(!string.IsNullOrEmpty(offerAudioBook.Format))
                writer.WriteElementString("format", offerAudioBook.Format);

            if(offerAudioBook.RecordingLength.HasValue)
                writer.WriteElementString("recording_length", offerAudioBook.RecordingLength.Value.ToString("mm.ss"));
        }

        private static void WriteOfferBook(OfferBook offerBook, XmlWriter writer)
        {
            if(!string.IsNullOrEmpty(offerBook.Author))
                writer.WriteElementString("author", offerBook.Author);

            writer.WriteElementString("name", offerBook.Name);

            if(!string.IsNullOrEmpty(offerBook.Publisher))
                writer.WriteElementString("publisher", offerBook.Publisher);

            if(!string.IsNullOrEmpty(offerBook.Series))
                writer.WriteElementString("series", offerBook.Series);

            if(offerBook.Year.HasValue)
                writer.WriteElementString("year", offerBook.Year.Value.ToString());

            if(!string.IsNullOrEmpty(offerBook.ISBN))
                writer.WriteElementString("ISBN", offerBook.ISBN);

            if(offerBook.Volume.HasValue)
                writer.WriteElementString("volume", offerBook.Volume.Value.ToString());

            if(offerBook.Part.HasValue)
                writer.WriteElementString("part", offerBook.Part.Value.ToString());

            if(!string.IsNullOrEmpty(offerBook.Language))
                writer.WriteElementString("language", offerBook.Language);

            if(!string.IsNullOrEmpty(offerBook.Binding))
                writer.WriteElementString("binding", offerBook.Binding);

            if(offerBook.PageExtent.HasValue)
                writer.WriteElementString("page_extent", offerBook.PageExtent.Value.ToString());

            if(!string.IsNullOrEmpty(offerBook.TableOfContents))
                writer.WriteElementString("table_of_contents", offerBook.TableOfContents);
        }

        private static void WriteOfferVendorModel(OfferVendorModel offerVendorModel, XmlWriter writer)
        {
            if(!string.IsNullOrEmpty(offerVendorModel.TypePrefix))
                writer.WriteElementString("typePrefix", offerVendorModel.TypePrefix);

            writer.WriteElementString("vendor", offerVendorModel.Vendor);

            if(!string.IsNullOrEmpty(offerVendorModel.VendorCode))
                writer.WriteElementString("vendorCode", offerVendorModel.VendorCode);

            writer.WriteElementString("model", offerVendorModel.Model);

            if(!string.IsNullOrEmpty(offerVendorModel.Provider))
                writer.WriteElementString("provider", offerVendorModel.Provider);

            if(!string.IsNullOrEmpty(offerVendorModel.Provider) && !string.IsNullOrEmpty(offerVendorModel.TarifPlan))
                writer.WriteElementString("tarifplan", offerVendorModel.TarifPlan);
        }

        private static void WriteOfferSimple(OfferSimple offerSimple, XmlWriter writer)
        {
            writer.WriteElementString("name", offerSimple.Name);

            if(!string.IsNullOrEmpty(offerSimple.Vendor))
                writer.WriteElementString("vendor", offerSimple.Vendor);

            if(!string.IsNullOrEmpty(offerSimple.VendorCode))
                writer.WriteElementString("vendorCode", offerSimple.VendorCode);
        }

        #endregion

        #region Offer Nested Classes

        private static void WriteOfferParams(IEnumerable<Param> param, XmlWriter writer)
        {
            foreach(Param paramObj in param)
            {
                writer.WriteStartElement("param");
                writer.WriteAttributeString("name", paramObj.Name);
                if(!string.IsNullOrEmpty(paramObj.Unit))
                    writer.WriteAttributeString("unit", paramObj.Unit);
                writer.WriteString(paramObj.Value);
                writer.WriteEndElement();
            }
        }

        private static void WriteOfferBarcodes(IEnumerable<uint> barcodes, XmlWriter writer)
        {
            foreach(uint barcode in barcodes)
                writer.WriteElementString("barcode", barcode.ToString());
        }

        private static void WriteOfferAdditionals(IEnumerable<string> additionals, XmlWriter writer)
        {
            foreach(string additionalValue in additionals)
                writer.WriteElementString("additional", additionalValue);
        }

        private static void WriteOfferCategories(IEnumerable<CategoryID> categoryIds, XmlWriter writer)
        {
            foreach(CategoryID categoryId in categoryIds)
            {
                writer.WriteStartElement("categoryId");
                writer.WriteAttributeString("type", categoryId.Type.ToString());
                writer.WriteString(categoryId.Value.ToString());
                writer.WriteEndElement();
            }
        }

        #region OrderingTime

        private static void WriteOfferOrderingTime(OrderingTime orderingTime, XmlWriter writer)
        {
            writer.WriteStartElement("orderingTime");

            if(!string.IsNullOrEmpty(orderingTime.OnStock))
                writer.WriteElementString("onstock", orderingTime.OnStock);

            WriteOfferOrderingTimeOrdering(orderingTime.Ordering, writer);

            if(!string.IsNullOrEmpty(orderingTime.DeliveryTime))
                writer.WriteElementString("deliveryTime", orderingTime.DeliveryTime);

            writer.WriteEndElement();
        }

        private static void WriteOfferOrderingTimeOrdering(Ordering ordering, XmlWriter writer)
        {
            writer.WriteStartElement("ordering");

            if(!string.IsNullOrEmpty(ordering.Hours))
                writer.WriteAttributeString("hours", ordering.Hours);

            writer.WriteString(ordering.Value);

            writer.WriteEndElement();
        }

        #endregion

        #endregion

        #endregion
    }
}